package com.flying.acls.domain.context;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.Nullable;
import org.springframework.security.util.FieldUtils;
import org.springframework.util.PropertyPlaceholderHelper;

import java.util.List;
import java.util.stream.Collectors;

@Data
@Slf4j
public class ContextStringValueResolver {
    private static final String DEFAULT_RESOURCE_TAG = "r";
    private static final String DEFAULT_PRINCIPAL_TAG = "s";
    private static final String DEFAULT_PERMISSION_TAG = "p";
    private static final String DEFAULT_TAG_SEPARATOR = ".";

    private String resourceTag = DEFAULT_RESOURCE_TAG;
    private String principalTag = DEFAULT_RESOURCE_TAG;
    private String permissionTag = DEFAULT_RESOURCE_TAG;
    private String tagSeparator = DEFAULT_TAG_SEPARATOR;

    private static final String DEFAULT_RESOURCE_CLASSNAME = "rcn";
    private static final String DEFAULT_RESOURCE_ID_PROPERTY = "id";
    private static final String DEFAULT_PRINCIPAL_CLASSNAME = "scn";
    private static final String DEFAULT_PERMISSION_CLASSNAME = "pcn";

    private String resourceClassName = DEFAULT_RESOURCE_CLASSNAME;
    private String resourceIdProperty = DEFAULT_RESOURCE_ID_PROPERTY;
    private String principalClassName = DEFAULT_PRINCIPAL_CLASSNAME;
    private String permissionClassname = DEFAULT_PERMISSION_CLASSNAME;

    public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
    public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
    public static final String DEFAULT_VALUE_SEPARATOR = ":";

    private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
    private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
    private String valueSeparator = DEFAULT_VALUE_SEPARATOR;

    private boolean ignoreUnresolvablePlaceholders = true;
    private PropertyPlaceholderHelper helper;

    public ContextStringValueResolver() {
        this.helper = new PropertyPlaceholderHelper(placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
    }

    public String resolveStringValue(ExpressionContext context, String strVal) {
        PropertyPlaceholderHelper.PlaceholderResolver resolver = new ContextPlaceholderResolver(context);
        return helper.replacePlaceholders(strVal, resolver);
    }

    public List<String> resolveStringValue(ExpressionContext context, List<String> strVals) {
        PropertyPlaceholderHelper.PlaceholderResolver resolver = new ContextPlaceholderResolver(context);
        return strVals.stream()
                .map(strVal -> helper.replacePlaceholders(strVal, resolver))
                .collect(Collectors.toList());
    }

    private class ContextPlaceholderResolver implements PropertyPlaceholderHelper.PlaceholderResolver {
        private ExpressionContext context;

        ContextPlaceholderResolver(ExpressionContext context) {
            this.context = context;
        }

        @Nullable
        @Override
        public String resolvePlaceholder(String placeholderName) {
            if (context == null) return null;
            // first to get value from env
            String resolved = context.getEnvVar(placeholderName);
            if (resolved != null) return resolved;

            // no value in env, retrieve from object property according to the tag sequence.
            String tag = StringUtils.substringBefore(placeholderName, tagSeparator);
            String property = StringUtils.substringAfter(placeholderName, tagSeparator);
            try {
                if (tag.equals(resourceTag)) {
                    resolved = FieldUtils.getFieldValue(context.getAccessingObject(), property).toString();
                } else if (tag.equals(principalTag)) {
                    resolved = FieldUtils.getFieldValue(context.getPrincipal(), property).toString();
                } else if (tag.equals(permissionTag)) {
                    resolved = FieldUtils.getFieldValue(context.getRequiredPermission(), property).toString();
                }
            } catch (Exception e) {
                log.warn("Exception in reading property:" + property, e);
            }
            if (resolved != null) {
                context.addEnvVar(placeholderName, resolved);
            }
            return resolved;
        }
    }
}